﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CodeSmith.Data.Linq
{
    /// <summary>
    /// A paged collection.
    /// </summary>
    /// <typeparam name="T">The type of the items in the list.</typeparam>
    /// <remarks>
    /// When this collection is created, IQueryable Skip and Take is
    /// caculated and called on the source list. Also, if total count 
    /// is not specified, IQueryable Count is called.
    /// </remarks>
    public class PagedList<T> : List<T>, IPageList<T>
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="PagedList&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="source">The source list of items.</param>
        /// <param name="pageIndex">Index of the page.</param>
        /// <param name="pageSize">Size of the page.</param>
        public PagedList(IEnumerable<T> source, int pageIndex, int pageSize)
            : this(source, pageIndex, pageSize, null)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PagedList&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="pageIndex">Index of the page.</param>
        /// <param name="pageSize">Size of the page.</param>
        /// <param name="totalCount">The total count.</param>
        public PagedList(IEnumerable<T> source, int pageIndex, int pageSize, int? totalCount)
        {
            Initialize(source.AsQueryable(), pageIndex, pageSize, totalCount);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PagedList&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="pageIndex">Index of the page.</param>
        /// <param name="pageSize">Size of the page.</param>
        public PagedList(IQueryable<T> source, int pageIndex, int pageSize)
            : this(source, pageIndex, pageSize, null)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PagedList&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="pageIndex">Index of the page.</param>
        /// <param name="pageSize">Size of the page.</param>
        /// <param name="totalCount">The total count.</param>
        public PagedList(IQueryable<T> source, int pageIndex, int pageSize, int? totalCount)
        {
            Initialize(source, pageIndex, pageSize, totalCount);
        }

        #region IPagedList Members

        /// <summary>
        /// Gets a value indicating whether this instance has previous page.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance has previous page; otherwise, <c>false</c>.
        /// </value>
        public bool HasPreviousPage { get; private set; }
        /// <summary>
        /// Gets a value indicating whether this instance has next page.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance has next page; otherwise, <c>false</c>.
        /// </value>
        public bool HasNextPage { get; private set; }
        /// <summary>
        /// Gets a value indicating whether this instance is first page.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is first page; otherwise, <c>false</c>.
        /// </value>
        public bool IsFirstPage { get; private set; }
        /// <summary>
        /// Gets a value indicating whether this instance is last page.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is last page; otherwise, <c>false</c>.
        /// </value>
        public bool IsLastPage { get; private set; }
        /// <summary>
        /// Gets the total page count.
        /// </summary>
        /// <value>The total page count.</value>
        public int PageCount { get; private set; }
        /// <summary>
        /// Gets the zero based index of the page.
        /// </summary>
        /// <value>The index of the page.</value>
        public int PageIndex { get; private set; }
        /// <summary>
        /// Gets the page number. Page number is PageIndex + 1.
        /// </summary>
        /// <value>The page number.</value>
        public int PageNumber { get { return PageIndex + 1; } }
        /// <summary>
        /// Gets the size of the page.
        /// </summary>
        /// <value>The size of the page.</value>
        public int PageSize { get; private set; }
        /// <summary>
        /// Gets the total item count.
        /// </summary>
        /// <value>The total item count.</value>
        public int TotalItemCount { get; private set; }

        #endregion

        /// <summary>
        /// Initializes the specified source.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="pageIndex">Index of the page.</param>
        /// <param name="pageSize">Size of the page.</param>
        /// <param name="totalCount">The total count.</param>
        protected void Initialize(IQueryable<T> source, int pageIndex, int pageSize, int? totalCount)
        {
            if (pageIndex < 0)
                throw new ArgumentOutOfRangeException("pageIndex", "PageIndex cannot be below 0.");
            if (pageSize < 1)
                throw new ArgumentOutOfRangeException("pageIndex", "PageSize cannot be less than 1.");

            if (source == null)
                source = new List<T>().AsQueryable();

            TotalItemCount = totalCount.HasValue ? totalCount.Value : source.Count();
            PageSize = pageSize;
            PageIndex = pageIndex;
            PageCount = TotalItemCount > 0 ? (int)Math.Ceiling(TotalItemCount / (double)PageSize) : 0;
            HasPreviousPage = (PageIndex > 0);
            HasNextPage = (PageIndex < (PageCount - 1));
            IsFirstPage = (PageIndex <= 0);
            IsLastPage = (PageIndex >= (PageCount - 1));

            if (TotalItemCount > 0)
                AddRange(source.Skip((pageIndex) * pageSize).Take(pageSize).ToList());
        }
    }
}
